package com.hqd.ch03.v51.boot.autoconfigure.web.servlet;

import com.hqd.ch03.utils.ObjectUtils;
import com.hqd.ch03.v51.aware.BeanFactoryAware;
import com.hqd.ch03.v51.beans.BeansException;
import com.hqd.ch03.v51.beans.factory.ObjectProvider;
import com.hqd.ch03.v51.beans.factory.config.ConfigurableListableBeanFactory;
import com.hqd.ch03.v51.boot.autoconfigure.AutoConfiguration;
import com.hqd.ch03.v51.boot.autoconfigure.AutoConfigureOrder;
import com.hqd.ch03.v51.boot.autoconfigure.condition.ConditionalOnClass;
import com.hqd.ch03.v51.boot.autoconfigure.condition.ConditionalOnProperty;
import com.hqd.ch03.v51.boot.autoconfigure.web.ServerProperties;
import com.hqd.ch03.v51.boot.context.annotation.ImportBeanDefinitionRegistrar;
import com.hqd.ch03.v51.boot.context.properties.EnableConfigurationProperties;
import com.hqd.ch03.v51.boot.web.filter.ForwardedHeaderFilter;
import com.hqd.ch03.v51.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import com.hqd.ch03.v51.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import com.hqd.ch03.v51.boot.web.servlet.FilterRegistrationBean;
import com.hqd.ch03.v51.boot.web.servlet.WebListenerRegistrar;
import com.hqd.ch03.v51.boot.web.servlet.server.CookieSameSiteSupplier;
import com.hqd.ch03.v51.context.annotation.Bean;
import com.hqd.ch03.v51.context.annotation.Configuration;
import com.hqd.ch03.v51.context.annotation.Import;
import com.hqd.ch03.v51.core.Ordered;
import com.hqd.ch03.v51.core.type.AnnotationMetadata;
import com.hqd.ch03.v51.factory.BeanFactory;
import com.hqd.ch03.v51.factory.support.RootBeanDefinition;
import com.hqd.ch03.v51.registry.BeanDefinitionRegistry;

import javax.servlet.DispatcherType;
import javax.servlet.ServletRequest;
import java.util.function.Supplier;
import java.util.stream.Collectors;


/**
 * Web容器工厂自动装配
 */
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
})
public class ServletWebServerFactoryAutoConfiguration {
    /**
     * 通用配置
     *
     * @param serverProperties
     * @param webListenerRegistrars
     * @param cookieSameSiteSuppliers
     * @return
     */
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
                                                                               ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
                                                                               ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
        return new ServletWebServerFactoryCustomizer(serverProperties,
                webListenerRegistrars.orderedStream().collect(Collectors.toList()),
                cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
    }

    /**
     * 只有是Tomcat的时候才生效
     *
     * @param serverProperties
     * @return
     */
    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    interface ForwardedHeaderFilterCustomizer {
        void customize(ForwardedHeaderFilter filter);
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
    @ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
    static class ForwardedHeaderFilterConfiguration {

        @Bean
        @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
        ForwardedHeaderFilterCustomizer tomcatForwardedHeaderFilterCustomizer(ServerProperties serverProperties) {
            return (filter) -> filter.setRelativeRedirects(serverProperties.getTomcat().isUseRelativeRedirects());
        }

        @Bean
        FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter(
                ObjectProvider<ForwardedHeaderFilterCustomizer> customizerProvider) {
            ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
            customizerProvider.ifAvailable((customizer) -> customizer.customize(filter));
            FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
            registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
            registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return registration;
        }

    }

    /**
     * BeanPostProcessors注册器
     */
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            //注册WebServerFactoryCustomizerBeanPostProcessor
            registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class,
                    WebServerFactoryCustomizerBeanPostProcessor::new);
            //注册ErrorPageRegistrarBeanPostProcessor
            registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
        }

        private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name,
                                                        Class<T> beanClass, Supplier<T> instanceSupplier) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

}